# Building HoloFan Studio — From Camera Dither Tool to Live Spinning Display

This documents the step-by-step build of the browser control app for the HoloFan project, tracing how it evolved from a basic camera-to-dither preview into a fully wireless live-streaming controller for the spinning 42-LED blade.

---

## Stage 1 — Basic camera-to-dither preview

The first version was a simple test: take a live webcam feed, run it through a dithering algorithm (Floyd-Steinberg style), and render the result on a canvas. No hardware connection yet — this was purely to validate that browser-side image processing could keep up in real time before building anything physical.

---

## Stage 2 — Mapping to the actual blade geometry

Once the dithering worked, the canvas was reshaped to match the real LED layout: a **42-LED strip arranged in concentric rings**, 5mm diameter pixels with 3mm gaps, matching the physical NeoPixel strip mounted along the spinning blade's diameter. This was the key conceptual shift — instead of a flat rectangular preview, the canvas now drew the image the way it would actually appear once the blade spins and sweeps the LEDs into a disc.

Each of the 36 angular "columns" (10° steps around a full rotation) maps to a specific frame of LED color data, and each LED's distance from the center LED (LED 21) maps to its radius on the disc.

---

## Stage 3 — Connecting to the XIAO over WiFi

With the visual mapping working, the next step was getting real LED data out of the browser and onto the blade's XIAO ESP32-S3. This required:

- The XIAO running in **WiFi Access Point mode**, broadcasting its own network (`HoloFan` SSID) rather than joining an existing router — this was necessary because phone hotspots isolate connected clients from each other, which blocked direct device-to-device communication
- A **WebSocket server** on the XIAO (port 81) receiving binary LED frame data
- The browser opening a `ws://192.168.4.1:81` connection once the laptop joined the XIAO's network

This is the point where the file became `holofan_live.html` — live camera frames were captured, converted into the polar LED format, and sent over the WebSocket as raw binary buffers (36 columns × 42 LEDs × 3 RGB bytes per frame).

---

## Stage 4 — RPM fine-tuning

Without a hardware sync sensor running yet, the displayed image would slowly drift/rotate because the column timing on the XIAO didn't perfectly match the blade's real RPM. To work around this live, an **RPM fine-tune slider** was added (500–2000 RPM range, plus ±1/±10 step buttons) that recalculates the per-column delay in microseconds and pushes it to the XIAO as a small 2-byte WebSocket message, letting the timing be adjusted live while the blade spins until the image visually locks into a stable zone.

---

## Stage 5 — Colour modes

Twelve colour transformation modes were added so the same live feed (or uploaded image) could be rendered differently: natural, black & white, invert, thermal, neon cyan, red fire, deep purple, golden, matrix green, ice blue, sunset, and x-ray. Each mode is a small function that remaps the sampled RGB values before they're packed into the frame buffer sent to the XIAO.

---

## Stage 6 — Merging in still-image upload and crop/position controls

A separate version of the controller (the "HoloFan Studio" themed one) had been built in parallel with a different feature set — image upload via drag-and-drop, and zoom/pan/angle-offset controls for positioning a static image within the disc. This was merged into the live-streaming version so the final tool supports **two source modes**:

- **Live Camera** — continuous capture, dithering, and streaming, same as Stage 3–5
- **Still Image** — upload a file, adjust zoom/pan/angle/brightness/contrast, preview on the polar disc, and send a single static frame to the blade with a "Send to Fan" button

The slider layout was also revised at this stage — sliders were initially too cramped/invisible, so they were rebuilt as full-width tracks with visible labels and live value readouts, placed below the pixel preview panel rather than squeezed alongside it.

---

## Final architecture

```
Browser (HoloFan Studio)
   ├── Camera tab  → live capture → polar sampling → colour mode → WebSocket frame
   └── Image tab   → file upload  → polar sampling → colour mode → WebSocket frame (on demand)
          │
          ▼
   ws://192.168.4.1:81
          │
          ▼
   XIAO ESP32-S3 (Access Point + WebSocket server)
          │
          ▼
   42-LED NeoPixel strip on spinning blade
```

**RPM slider** sends a separate small 2-byte timing packet independently of image frames, so the column delay can be tuned without re-sending the whole image.

---

## What's stable vs. what still drifts

- **WebSocket connection and live streaming** — working reliably once the XIAO is in AP mode and the laptop joins its network directly (this fixed the earlier failures caused by browser mixed-content blocking and phone-hotspot client isolation)
- **Colour modes, brightness/contrast, zoom/pan/angle** — all working and immediately visible in the preview
- **RPM-based image stability** — only approximate; manually tuning the slider finds "sweet spot" zones where the image looks mostly still, but there's no automatic phase-lock yet. That's the piece the AS5600 encoder integration is meant to eventually solve.

---

## Reference files

The two HTML files referenced in this document are attached alongside this markdown:

- `holofan_live.html` — the live-streaming version with RPM tuning and colour modes (Stages 1–5)
- `holofan_controller.html` — the parallel "Studio" version with upload, zoom/pan/angle controls, before merging
